﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Threading;
using DeviceSync.ecclescollege;
using System.Security.Cryptography;

namespace DeviceSync
{
	public partial class MainForm : Form
	{
		private static readonly DateTime coIgnoreFilesBefore = new DateTime(2008, 1, 15);
		private const long coMaxFileSize = 1000000L;
		private const int coFragmentSize = 16000;
		
		private Thread keepawakethread;
		private volatile bool keepawake;
		SHA1Managed hasher = new SHA1Managed();

		public MainForm()
		{
			InitializeComponent();

			label2.Text = "Version " + GetVersion().ToString();

			keepawakethread = new Thread(delegate()
				{
					// Thread to keep the system awake while transfer takes place
					for (; ; )
					{
						Thread.Sleep(25000);
						if (keepawake)
							WinAPICalls.SystemIdleTimerReset();
					}
				});

			if (Program.loginDets == null)
				GetCredentials("Please login", false);

			keepawakethread.IsBackground = true;
			keepawakethread.Priority = ThreadPriority.AboveNormal;
			keepawakethread.Start();
		}

		/// <summary>
		/// Prompt for login until they click OK
		/// </summary>
		private void GetCredentials(string msg, bool allowcancel)
		{
			LoginForm login = new LoginForm(msg);
			if (!allowcancel)
				login.HideCancel();
			login.ShowDialog();
		}

		/// <summary>
		/// Get filename for sync log. This is user specific, so we can sync with several student accounts
		/// </summary>
		private string GetLocalCacheName()
		{
			string s = Program.loginDets.Username.Trim();
			if (string.IsNullOrEmpty(s)) throw new Exception("No username specified");

			return "\\sync-" + s.Replace('.','-') + ".dat";
		}

		private void DoSync()
		{
			label1.Text = "Looking for changes...";
			Update();

			FileScanner scanner = new FileScanner();
			scanner.LoadCache(GetLocalCacheName());
			bool changed = false;

			// get a list of all interesting local files (within size limits, etc)
			var localfiles = WalkFolders("\\My Documents")
				.Where(f => f.LastWriteTime >= coIgnoreFilesBefore && f.Length > 0L && f.Length <= coMaxFileSize && !f.Name.ToLower().EndsWith(".exe"));

			// compare these to already-synced files, and get the changes
			var neededfiles = scanner.UnsyncedFiles(localfiles)
				.OrderBy(f => f.Length)
				.ToArray();

			if (neededfiles.Length == 0)
			{
				label1.Text = "Nothing to sync";
				return;
			}

			fileList.DisplayMember = "Name";
			fileList.Items.Clear();
			foreach(FileInfo info in neededfiles)
				fileList.Items.Add(info);

			label1.Text = "Authenticating...";
			Update();

			try
			{
				try
				{
					using (var client = Program.GetClient())
					{
						if (!StartupChecks(client)) return;	// bail out if the SOAP service needs a higher version

						label1.Text = "Authenticated - sending files...";
						Update();

						foreach (FileInfo info in neededfiles)
						{
							try
							{
								// send file header to server
								int fileid;
								bool fileidgiven;
								client.SendFileRec(info.FullName, info.LastWriteTime, true, out fileid, out fileidgiven);
								if (!fileidgiven) throw new Exception("No fileid returned");

								// send file contents, as a set of fragments
								SendFile(client, info, fileid);

								// Add the file to local list of synced items
								scanner.AddFile(info);

								label1.Text = "Sent: " + info.Name;
								fileList.Items.Remove(info);
								Update();

								changed = true;
							}
							catch
							{
								label1.Text = "Error syncing " + info.Name;
							}
						}
					}
				}
				finally
				{
					if (changed)
						scanner.SaveCache(GetLocalCacheName());
				}

				if (fileList.Items.Count > 0)
					label1.Text = "Not completed. Try again";
				else
					label1.Text = "Finished";
			}
			catch (Exception)
			{
				label1.Text = "Failed to sync. Try again";
				GetCredentials("Invalid login", false);
			}
		}

		/// <summary>
		/// Sync device time with server, and confirm version is suitable. Returns TRUE if ok to continue
		/// </summary>
		private bool StartupChecks(SyncService client)
		{
			DateTime servertime;
			bool stspecified;
			DeviceSync.ecclescollege.Version reqver;

			DateTime before = DateTime.Now;
			client.StartupChecks(out servertime, out stspecified, out reqver);
			if (stspecified)
			{
				double ms = DateTime.Now.Subtract(before).TotalMilliseconds / 2.0;
				DateTime newtime = servertime.AddMilliseconds(ms);
				WinAPICalls.WinCESetSystemTime(WinAPICalls.ToSysTime(newtime));
			}

			System.Version ver = ToSysVersion(reqver);
			if (GetVersion() < ver)
			{
				MessageBox.Show("UPGRADE REQUIRED! To v" + ver.ToString());
				Update();
				return false;
			}

			return true;
		}

		/// <summary>
		/// Send a single file to the service, in chunks
		/// </summary>
		private void SendFile(SyncService serv, FileInfo file, int fileid)
		{
			progressBar1.Value = 0;
			progressBar1.Maximum = (int)(file.Length / 100);
			try
			{
				using (FileStream fStream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read))
				{
					using (BinaryReader br = new BinaryReader(fStream))
					{
						int fragment = 0;
						byte[] buff;
						if (file.Length > coFragmentSize)
							buff = new byte[coFragmentSize];		// multi-fragment file
						else
							buff = new byte[file.Length];			// single fragment, no point in oversized array, it just slows things down
						int num = -1;
						double sent = 0;

						int frags = (int)(file.Length / buff.Length);
						if (file.Length % buff.Length != 0)
							++frags;

						while ((num = br.Read(buff, 0, buff.Length)) > 0)
						{
							if (fragment > 0)
							{
								if (!progressBar1.Visible)
									progressBar1.Visible = true;
								label1.Text = file.Name + " sent " + (sent / file.Length).ToString("p0");
								progressBar1.Value = (int)(sent / 100);
								Update();
							}
							
							string localhash = HashData(buff, num);
							serv.SendFragmentHashClosure(fileid, true, fragment, true, buff, num, true, localhash, (fragment + 1 >= frags), true);

							++fragment;
							sent += num;
						}
					}
				}
			}
			finally
			{
				progressBar1.Visible = false;
			}
		}

		/// <summary>
		/// Gets all files in specified folder
		/// </summary>
		private IEnumerable<FileInfo> WalkFolders(string f)
		{
			DirectoryInfo info = new DirectoryInfo(f);
			FileInfo[] files = info.GetFiles();
			foreach (FileInfo i in files)
				yield return i;

			DirectoryInfo[] dirs = info.GetDirectories();
			foreach (DirectoryInfo i in dirs)
			{
				foreach (FileInfo inf in WalkFolders(i.FullName))
					yield return inf;
			}
		}

		private void button1_Click(object sender, EventArgs e)
		{
			if (Program.loginDets == null || string.IsNullOrEmpty(Program.loginDets.Username))
			{
				GetCredentials("Please login", false);
				return;
			}

			string temp = button1.Text;
			try
			{
				button1.Text = "Working...";
				button1.Enabled = false;
				menuItem1.Enabled = false;
				keepawake = true;

				DoSync();
			}
			finally
			{
				keepawake = false;
				button1.Text = temp;
				button1.Enabled = true;
				menuItem1.Enabled = true;
			}
		}

		public static System.Version GetVersion()
		{
			return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
		}

		private static System.Version ToSysVersion(DeviceSync.ecclescollege.Version v)
		{
			return new System.Version(v._Major, v._Minor, v._Build, v._Revision);
		}

		/// <summary>
		/// Returns base64 encoded SHA1 hash of the supplied data
		/// </summary>
		private string HashData(byte[] data, int len)
		{
			if (data == null || data.Length == 0) return "";
			return System.Convert.ToBase64String(hasher.ComputeHash(data, 0, len));
		}

		private void button2_Click(object sender, EventArgs e)
		{
			GetCredentials("New username", true);
		}

		private void menuItem2_Click(object sender, EventArgs e)
		{
			MessageBox.Show("DeviceSync - (c) 2008 Eccles College. Developed for the MoLeNet project by John Sparrow");
		}
	}
}